package org.axed.user.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;

/**
 * Anything in Axed that spawns over / tags document objects.
 *
 * That is:
 *  Selection
 *
 * That will be:
 *  Bold
 *  Italic
 *  all other fancy stuff
 */
public abstract class Range {
	private Anchor beginAnchor = null, endAnchor = null;
	private Anchor anchor1 = null, anchor2 = null;

	public Line setA1line = null, setA2line = null;
	public int  setA1col  = -1,   setA2col  = -1;
	private boolean setA1change = false, setA2change = false;
	
	/**
	 * true when this range is visible
	 */
	private boolean visible = false; 

	/**
	 * The parent document of this range
	 */
	private Document parent;

	/**
	 * True if setA2 later in doc than setA1
	 */
	private boolean forward;

	/**
	 * Constructor
	 */
	public Range(Document parent) {
		this.parent = parent;
		anchor1 = new Anchor(this);
		anchor2 = new Anchor(this);
	}

	/**
	 * Applies this anchor setting to the range, and the range to the document.
	 */
	public void commit() {
		if (!isSet()) {
			throw new AxedInternalError("committing unset range");
		}
		Line oldBeginLine;
		Line oldEndLine;

		if (forward) {
			oldBeginLine = anchor1.line;
			oldEndLine   = anchor2.line;
		} else {
			oldBeginLine = anchor2.line;
			oldEndLine   = anchor1.line;
		}
  
		if (setA1change) {
			if (setA1line.hasRange(this)) {
				setA1line.removeRange(this);
			}
			anchor1.set(setA1line, setA1col);
		}
		if (setA2change) {
			if (setA2line.hasRange(this)) {
				setA2line.removeRange(this);
			}
			anchor2.set(setA2line, setA2col);
		}

		forward = anchor2.compare(anchor1) >= 0;
	
		if (forward) {
			beginAnchor = anchor1;
			endAnchor = anchor2;
			if (beginAnchor.line != endAnchor.line) {
				setA1line.lineStr.addTermAnchor(anchor1);
				setA2line.lineStr.addInitAnchor(anchor2);
			}
		} else {
			beginAnchor = anchor2;
			endAnchor = anchor1;
			if (beginAnchor.line != endAnchor.line) {
				setA2line.lineStr.addTermAnchor(anchor2);
				setA1line.lineStr.addInitAnchor(anchor1);
			}
		}
		if (beginAnchor.line == endAnchor.line) {
			setA1line.lineStr.removeInitTerm(anchor1);
			setA1line.lineStr.removeInitTerm(anchor2);
		}

		setA1change = false;
		setA2change = false;

		if (oldBeginLine != null) {
			int ob = oldBeginLine.getLineNum() + 1;
			int oe = oldEndLine.getLineNum();
			int nb = beginAnchor.line.getLineNum() + 1;
			int ne = endAnchor.line.getLineNum();

			// cut away all old range entries infront that are now longer spawned over.
			for(int i = ob; i < nb && i < oe; i++) {
				Line l = parent.getLine(i);
				l.removeRange(this);
				l.refresh();
			}
    
			// cut away all old range entries therafter that are now longer spawned over.
			for(int i = ne > ob ? ne : ob; i < oe; i++) {
				Line l = parent.getLine(i);
				l.removeRange(this);
				l.refresh();
			}
    
			//insert new range entries before the old spawn.
			for(int i = nb; i < ob && i < ne; i++) {
				Line l = parent.getLine(i);
				l.addRange(this);
				l.refresh();
			}
    
			//insert new range entries after the old spawn.
			for(int i = oe > nb ? oe : nb; i < ne; i++) {
				Line l = parent.getLine(i);
				l.addRange(this);
				l.refresh();
			}
		} else {
			int nb = beginAnchor.line.getLineNum() + 1;
			int ne = endAnchor.line.getLineNum();
    
			// this is easy just add new spawns.
			for(int i = nb; i < ne; i++) {
				Line l = parent.getLine(i);
				l.addRange(this);
				l.refresh();
			}
		}

		beginAnchor.commit();
		endAnchor.commit();

		beginAnchor.line.refresh();
		if (beginAnchor.line != endAnchor.line) {
				endAnchor.line.refresh();
		}
	}

	/**
	 * Retrieves the contents of the anchor earlier in the document.
	 */
	public Anchor getBegin() {
		return beginAnchor;	
	}

	/**
 	 * Returns the HTML text for start of this range.
 	 */
	public abstract String getBeginText();

	/**
	 * Retrieves the contents of the anchor later in the document.
	 */
	public Anchor getEnd() {
		return endAnchor;
	}
	
	/**
	 * Returns the HTML text for end of this range.
	 */
	public abstract String getEndText();

	/**
	 * Return the HTML text for Anchor a, depending if its the start or end
	 * anchor, or null if its neither.
	 */
	public String getText(Anchor a) {
		if (a == null) {
			return null;
		}
		if (!visible) {
			return "";
		}
		if (a == beginAnchor) {
			return getBeginText();
		}
		if (a == endAnchor) {
			return getEndText();
		}
//		GWT.log("getText(anchor) doesnt find anchor", null);
		return "";
//		return null;
	}

	/**
	 * Returns true if this range is limited to one line.
	 */
	public boolean isOneLined() {
		if (beginAnchor.line == null) {
			return false;
		}
		return beginAnchor.line == endAnchor.line;
	}

	/**
	 * Returns true if either anchor is unset, or both point at
	 * the same position.
	 */
	public boolean isEmpty() {
		if (anchor1.line == null || anchor2.line == null) {
			return true;
		}
		int c1 = anchor1.getCol();
		int c2 = anchor2.getCol();
		if (anchor1.line == anchor2.line &&
			c1 != -1 && 
			c1 == c2) {
			return true;
		}
		return false;
	}

	/**
	 * Returns true if both anchors are set.
	 */
	public boolean isSet() {
		return (setA1line != null) && (setA2line != null);
	}
       
	/**
	 * Returns true if both anchors are set.
	 */
	public boolean isVisible() {
		return visible;
	}

	/**
	 * Sets anchor1 to line and col.
	 */
	public void setAnchor1(Line line, int col) {
		setA1line = line;
		setA1col = col;
		setA1change = true;
	}
	
	/**
	 * Sets anchor1 to line and col.
	 */
	public void setAnchor1(int line, int col) {
		setAnchor1(parent.getLine(line), col);
	}

	/**
	 * Sets anchor1 to pointer.
	 */
	public void setAnchor1(AxedPointer p) {
		setAnchor1(parent.getLine(p.line), p.col);
	}

	/**
	 * Sets anchor 2 to line and col.
	 */
	public void setAnchor2(Line line, int col) {
		setA2line = line;
		setA2col = col;
		setA2change = true;
	}

	/**
	 * Sets anchor 2 to line and col.
	 */
	public void setAnchor2(int line, int col) {
		setAnchor2(parent.getLine(line), col);
	}

	/**
	 * Sets anchor 2 to pointer.
	 */
	public void setAnchor2(AxedPointer p) {
		setAnchor2(parent.getLine(p.line), p.col);
	}

	/**
	 * Sets this range visible.
	 */
	public void setVisible(boolean v) {
		visible = v;
	}

	/**
	 * Removes this range from the document, and sets both anchors to nil.
	 */
	public void unset() {
		if (beginAnchor != null && endAnchor != null) {
			int nb = beginAnchor.line.getLineNum() + 1;
			int ne = endAnchor.line.getLineNum();
    
			for(int i = nb; i < ne; i++) {
				Line l = parent.getLine(i);
				l.removeRange(this);
				l.refresh();
			}
		}

		beginAnchor = null; 
		endAnchor = null;

		anchor1.unset();
		anchor2.unset();

		setA1line = null;
		setA2line = null;

		setA1col  = -1;
		setA2col  = -1;
		
		setA1change = false;
		setA2change = false;

		visible = false;
	}

}

